/**	\author    coidgen 0.9.2
	\version    (4.0 build 10)
	\date      
	\brief     
*/



#include "clientRegistrator_client.h"
#include "coid/comm/binstream/container_linear.h"




namespace coid {

struct COID_TABLE_ClientDatabase_4_0  {
	opcd (COID_TABLE_ClientDatabase_4_0::*disconnect)();
	opcd (COID_TABLE_ClientDatabase_4_0::*add_user__0)( person & person_in );
	opcd (COID_TABLE_ClientDatabase_4_0::*set_address__1)( address_struct & address_in );
};

}	// namespace coid



#define ___COID_CONNECTWITHIN_TRY		try {
#define ___COID_CONNECTWITHIN_CATCH	} catch( opcd ___coid_err ) {return ___coid_err;}\
										catch(...) { return ersFE_EXCEPTION "client method exception"; }
#define ___COID_CONNECT_TRY		try {
#define ___COID_CONNECT_CATCH		} catch( opcd ___coid_err ) {delete _coid_bstream; return ___coid_err;}\
									catch( ThreadException & ) { delete _coid_bstream; throw; }\
									catch(...) { delete _coid_bstream; return ersFE_EXCEPTION "client method exception"; }





const version COID_version_ClientDatabase_4_0( "", 4, 0, 10 );

static COID_NICK g_coid_nick;

namespace coid {

static COID_TABLE_ClientDatabase_4_0 * coid_interprocess_vtbl = NULL;
static COID_TABLE_ClientDatabase_4_0 * coid_remote_vtbl = NULL;
static comm_mutex coid_table_mx( true, "COID_TABLE_ClientDatabase_4_0" );

struct TABLE_REMOVER_ClientDatabase_4_0 {
	~TABLE_REMOVER_ClientDatabase_4_0() {delete coid_interprocess_vtbl; delete coid_remote_vtbl;}
};
static TABLE_REMOVER_ClientDatabase_4_0	ClientDatabase_4_0_table_trash;





enum {	/// command ids, format is: COID_classname_fncname_fncid
	COID_ClientDatabase_add_user_0 = 0,
	COID_ClientDatabase_set_address_1 = 1
};




static uint COID_method_flags_ClientDatabase[2] = {
	0x70000,
	0x70000
};




/***************************************************************************
ClientDatabase_localclient
***************************************************************************/

class ClientDatabase_localclient
{
public:
	COID_CLIENT cc;

	static void coid_init_table( uint coid_stream_flags, COID_TABLE_ClientDatabase_4_0 *& table );
public:
   ClientDatabase_localclient( netstream * b, binstream * b2 ) : cc(b, b2) {}
   void coid_throw();
   opcd disconnect() { return cc.disconnect(); }


	opcd add_user( person & person_in );
	opcd set_address( address_struct & address_in );
};



/// fake function:
void ClientDatabase_localclient::coid_throw() {throw ersFE_EXCEPTION;}


typedef void (ClientDatabase_localclient::*coid_fnc)();

#define COID_CLIENT_CREATE_HOOK(M,i,t,tc)\
	if( COID_method_flags_ClientDatabase[i] & coid_stream_flags ) ((coid_fnc *) table)[t] = (coid_fnc) (tc) &ClientDatabase_localclient::M;\
	else ((coid_fnc *) table)[t] = (coid_fnc) &ClientDatabase_localclient::coid_throw;

#define COID_CLIENT_CREATE_DISCONNECT_HOOK(t)\
	((coid_fnc *) table)[t] = (coid_fnc) &ClientDatabase_localclient::disconnect;





void inline ClientDatabase_localclient::coid_init_table( uint coid_stream_flags, COID_TABLE_ClientDatabase_4_0 *& table )
{
	table = new COID_TABLE_ClientDatabase_4_0;

	uint coid_table_index = 0;
	COID_CLIENT_CREATE_DISCONNECT_HOOK( coid_table_index++ )
	COID_CLIENT_CREATE_HOOK( add_user, 0, coid_table_index++, opcd (ClientDatabase_localclient::*)( person &  ) )
	COID_CLIENT_CREATE_HOOK( set_address, 1, coid_table_index++, opcd (ClientDatabase_localclient::*)( address_struct &  ) )
}






opcd ClientDatabase_localclient::add_user( person & person_in )
{
	*cc._coid_bstream << (uint) COID_ClientDatabase_add_user_0;
	*cc._coid_bstream << cc._coid_inst_id;
	*cc._coid_bstream << person_in;

	cc._coid_bstream->flush();
	cc._coid_err_code = cc._coid_bstream->get_error();
	if( cc._coid_err_code ) {
		cc._coid_bstream->acknowledge();
		return cc._coid_err_code;
	}

	cc._coid_bstream->acknowledge();

	return cc._coid_err_code;
}



opcd ClientDatabase_localclient::set_address( address_struct & address_in )
{
	*cc._coid_bstream << (uint) COID_ClientDatabase_set_address_1;
	*cc._coid_bstream << cc._coid_inst_id;
	*cc._coid_bstream << address_in;

	cc._coid_bstream->flush();
	cc._coid_err_code = cc._coid_bstream->get_error();
	if( cc._coid_err_code ) {
		cc._coid_bstream->acknowledge();
		return cc._coid_err_code;
	}

	cc._coid_bstream->acknowledge();

	return cc._coid_err_code;
}




/***************************************************************************
ClientDatabase_client
***************************************************************************/

ClientDatabase_client::ClientDatabase_client() : _me(NULL), _vtbl(NULL), _conn_data(0), _timeout(0)
{
	netSubsystem::instance();
}
ClientDatabase_client::~ClientDatabase_client()
{
	disconnect();
}
ClientDatabase_client::ClientDatabase_client( const ClientDatabase_client & c ) {throw ersFE_EXCEPTION;}
ClientDatabase_client & ClientDatabase_client::operator = ( const ClientDatabase_client & c ) {throw ersFE_EXCEPTION; return *this;}

void ClientDatabase_client::destroy_me()
{
	if( get_connection_type() != ConnectFlags::fACCESS_MODE_DIRECT )
		delete (ClientDatabase_localclient *) _me;
	_me = NULL;
}

opcd ClientDatabase_client::disconnect()
{
	if( !is_connected() )
		return 0;
	DASSERT( _mx_reg.is_valid() );
	comm_mutex & lowlevelmx = _mx_reg._mxc->_mx;
	bool last = false;
	opcd coid_err;
	try {
		extendedGUARD_lock( _mx_reg );
		DASSERT( _me );
		coid_err = ((get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ? (COID_TABLE_ClientDatabase_4_0 *) this : _me)->*(_vtbl->disconnect)) ();
		destroy_me();
		if( _mx_reg.counter_fast() == 1 ) {
			_mx_reg.disable_fast();
			lowlevelmx.unlock();
			__coid__mxg.eject();
			last = true;
		}
	}
	catch( opcd e ) {
		destroy_me(); coid_err = e;
	}

	if( last ) {
#ifdef _DEBUG
		const int max = 0xFFFF;
#else
		const int max = 10;
#endif
		int i=0;
		for( ; i<max; i++ ) {
			if( lowlevelmx.try_lock() ) {lowlevelmx.unlock(); break;}
			sysMilliSecondSleep( 10 );
		}
		if( i == max ) throw ersFE_EXCEPTION;
	}

	_mx_reg.destroy_all();
	_vtbl = NULL;
	set_connection_type( 0 );
	return coid_err;
}

opcd ClientDatabase_client::get_last_error() const
{
	if( !is_connected() ) return 0;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) return ((COID_CLIENT *) _me)->get_last_error();
	else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) return ((COID_CLIENT *) _me)->get_last_error();
	return 0;
}

void ClientDatabase_client::set_last_error( opcd e )
{
	if( !is_connected() ) return;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) ((COID_CLIENT *) _me)->set_last_error( e );
	else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) ((COID_CLIENT *) _me)->set_last_error( e );
}

inline bool ClientDatabase_client::setup_members( uint coid_stream_flags, netstream * bstream, binstream * bstream2, comm_mutex_reg * mx_reg )
{
	disconnect();
	set_connection_type( coid_stream_flags );

	if( coid_stream_flags == ConnectFlags::fACCESS_MODE_DIRECT ) {	/// direct
		if( !bstream2 ) bstream2 = bstream;
		bstream2->read_ptr(_vtbl);
		bstream2->read_ptr(_me);
		comm_mutex_reg::refmutex* _rmx;
		bstream2->read_ptr(_rmx);
		_mx_reg.init( *_rmx );
		bstream2->acknowledge();
		delete bstream;
	}
	else if( coid_stream_flags == ConnectFlags::fACCESS_MODE_INTERPROCESS ) {	/// interprocess
		if( !bstream2 ) bstream2 = bstream;
		if( coid_interprocess_vtbl ) _vtbl = coid_interprocess_vtbl;
		else {
			MXGUARD( coid_table_mx );
			if( !coid_interprocess_vtbl )
				ClientDatabase_localclient::coid_init_table( coid_stream_flags, coid_interprocess_vtbl );
			_vtbl = coid_interprocess_vtbl;
		}
		_me = (COID_TABLE_ClientDatabase_4_0 *) new ClientDatabase_localclient( bstream, bstream2 );
		bstream2->acknowledge();
		_mx_reg.init( mx_reg, true, "ClientDatabase_client" );
		_mx_reg._p1 = bstream;
	}
	else if( coid_stream_flags == ConnectFlags::fACCESS_MODE_REMOTE ) {	/// remote
		if( !bstream2 ) bstream2 = bstream;
		if( coid_remote_vtbl ) _vtbl = coid_remote_vtbl;
		else {
			MXGUARD( coid_table_mx );
			if( !coid_remote_vtbl )
				ClientDatabase_localclient::coid_init_table( coid_stream_flags, coid_remote_vtbl );
			_vtbl = coid_remote_vtbl;
		}
		_me = (COID_TABLE_ClientDatabase_4_0 *) new ClientDatabase_localclient( bstream, bstream2 );
		bstream2->acknowledge();
		_mx_reg.init( mx_reg, true, "ClientDatabase_client" );
		_mx_reg._p1 = bstream;
	}
	else throw ersFE_EXCEPTION;

	return true;
}

void ClientDatabase_client::setup_stream( binstream& b, uint coid_flags, uint coid_obj_id ) const
{
	if( coid_flags & ConnectFlags::fWITHIN_CHANNEL ) {
		coid_flags &= ~ConnectFlags::fWITHIN_CHANNEL;
		b << StdProtocolMethod::CONNECT;
	}
	uchar conchar = StdProtocolMethod::CONNECT_CHAR;
	b << conchar << version(COID_VERSION);
	b << g_coid_nick << coid_flags << COID_version_ClientDatabase_4_0 << "ClientDatabase";
	b << (uint) 2;
   b.write_ptr(COID_method_flags_ClientDatabase);
   b << coid_obj_id << sysGetPid();
   b.write_ptr(this);
}

opcd ClientDatabase_client::ping_server( const char * coid_address, uint coid_obj_id, uint coid_flags ) const
{
	coid_flags |= ConnectFlags::fPING_SERVER;
	charstr tmp;
	netstream * _coid_bstream = COID_CLIENT::create_stream( "ClientDatabase", coid_address, tmp, (coid_flags & ConnectFlags::fTUNNEL) != 0 );
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	if( _timeout ) _coid_bstream->set_timeout( _timeout );
	coid_flags &= ~ConnectFlags::fTUNNEL;
	setup_stream( *_coid_bstream, coid_flags, coid_obj_id );
	___COID_CONNECT_TRY
	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	_coid_bstream->acknowledge();
	delete _coid_bstream;
	return _coid_err_code;

	___COID_CONNECT_CATCH
}

opcd ClientDatabase_client::set_timeout( uint milliseconds )
{
	if( milliseconds == _timeout ) return 0;
	_timeout = milliseconds;
	if( !is_connected() ) return 0;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ) return 0;
	netstream * n = ((COID_CLIENT *) _me)->get_stream();
	if( !n ) return ersFAILED;
	return n->set_timeout( _timeout );
}








opcd ClientDatabase_client::connect( std::vector< birth_date > & birt_d, const char * coid_address, uint coid_flags )
{
	_addr.reset();
	extendedGUARD_reg __coid__mxg( _mx_reg, false );
	if( _mx_reg.is_valid() ) {
		if( get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ) {	/// direct
			return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) {	/// interprocess
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) {	/// remote
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
	}

	netstream * _coid_bstream = COID_CLIENT::create_stream( "ClientDatabase", coid_address, _addr, (coid_flags & ConnectFlags::fTUNNEL) != 0 );
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	if( _timeout ) _coid_bstream->set_timeout( _timeout );
	coid_flags &= ~ConnectFlags::fTUNNEL;
	setup_stream( *_coid_bstream, coid_flags, UMAX );
	___COID_CONNECT_TRY
	*_coid_bstream << birt_d;

	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	if( _coid_err_code ) {
		_coid_bstream->acknowledge(true);
		delete _coid_bstream;
		return _coid_err_code;
	}

	uint coid_stream_flags;
	*_coid_bstream >> coid_stream_flags;
	RASSERT( coid_stream_flags & ConnectFlags::xACCESS_MODE );
	setup_members( coid_stream_flags, _coid_bstream, _coid_bstream );

	return _coid_err_code;

	___COID_CONNECT_CATCH
}








opcd ClientDatabase_client::connect_within( comm_mutex_reg & coid_channel, std::vector< birth_date > & birt_d )
{
	if( !coid_channel.is_set() ) return ersFE_CHANNEL;
	_addr.reset();
	extendedGUARD_reg __coid__mxg( coid_channel, true );

	netstream * _coid_bstream = (netstream *) ((comm_mutex_custom_reg<netstream, void*> &) coid_channel)._p1;
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	/// we must avoid direct access mode
	setup_stream( *_coid_bstream, ConnectFlags::fACCESS_MODE_REMOTE | ConnectFlags::fACCESS_MODE_INTERPROCESS | ConnectFlags::fWITHIN_CHANNEL, UMAX );
	___COID_CONNECTWITHIN_TRY
	*_coid_bstream << birt_d;

	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	if( _coid_err_code ) {
		_coid_bstream->acknowledge();
		return _coid_err_code;
	}

	uint coid_stream_flags;
	*_coid_bstream >> coid_stream_flags;
	RASSERT( coid_stream_flags & ConnectFlags::xACCESS_MODE );
	setup_members( coid_stream_flags, _coid_bstream, _coid_bstream, &coid_channel );

	return _coid_err_code;

	___COID_CONNECTWITHIN_CATCH
}






opcd ClientDatabase_client::connect_shared( uint coid_obj_id,  const char * coid_address, uint coid_flags  )
{
	_addr.reset();
	extendedGUARD_reg __coid__mxg( _mx_reg, false );
	if( _mx_reg.is_valid() ) {
		if( get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ) {	/// direct
			return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) {	/// interprocess
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) {	/// remote
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
	}

	netstream * _coid_bstream = COID_CLIENT::create_stream( "ClientDatabase", coid_address, _addr, (coid_flags & ConnectFlags::fTUNNEL) != 0 );
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	if( _timeout ) _coid_bstream->set_timeout( _timeout );
	coid_flags &= ~ConnectFlags::fTUNNEL;
	setup_stream( *_coid_bstream, coid_flags, coid_obj_id );
	___COID_CONNECT_TRY

	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	if( _coid_err_code ) {
		_coid_bstream->acknowledge(true);
		delete _coid_bstream;
		return _coid_err_code;
	}

	uint coid_stream_flags;
	*_coid_bstream >> coid_stream_flags;
	RASSERT( coid_stream_flags & ConnectFlags::xACCESS_MODE );
	setup_members( coid_stream_flags, _coid_bstream, _coid_bstream );

	return _coid_err_code;

	___COID_CONNECT_CATCH
}









opcd ClientDatabase_client::add_user( person & person_in )
{
	opcd _coid_err_code;
#ifndef ClientDatabase_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->add_user__0)) (person_in);
#ifndef ClientDatabase_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}

opcd ClientDatabase_client::set_address( address_struct & address_in )
{
	opcd _coid_err_code;
#ifndef ClientDatabase_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->set_address__1)) (address_in);
#ifndef ClientDatabase_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}



}	/// namespace coid
